diff options
Diffstat (limited to 'app/[lng]/procurement/(procurement)')
25 files changed, 184 insertions, 1447 deletions
diff --git a/app/[lng]/procurement/(procurement)/bid-projects/page.tsx b/app/[lng]/procurement/(procurement)/bid-projects/page.tsx deleted file mode 100644 index 2039e5b2..00000000 --- a/app/[lng]/procurement/(procurement)/bid-projects/page.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import * as React from "react" -import { type SearchParams } from "@/types/table" - -import { getValidFilters } from "@/lib/data-table" -import { Skeleton } from "@/components/ui/skeleton" -import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" -import { Shell } from "@/components/shell" -import { getBidProjectLists } from "@/lib/bidding-projects/service" -import { searchParamsBidProjectsCache } from "@/lib/bidding-projects/validation" -import { BidProjectsTable } from "@/lib/bidding-projects/table/projects-table" - - -interface IndexPageProps { - searchParams: Promise<SearchParams> -} - -export default async function IndexPage(props: IndexPageProps) { - const searchParams = await props.searchParams - const search = searchParamsBidProjectsCache.parse(searchParams) - - const validFilters = getValidFilters(search.filters) - - const promises = Promise.all([ - getBidProjectLists({ - ...search, - filters: validFilters, - }), - - ]) - - return ( - <Shell className="gap-2"> - <div className="flex items-center justify-between space-y-2"> - <div className="flex items-center justify-between space-y-2"> - <div> - <h2 className="text-2xl font-bold tracking-tight"> - 견적 프로젝트 리스트 - </h2> - <p className="text-muted-foreground"> - SAP(S-ERP)로부터 수신한 견적 프로젝트 데이터입니다. 기술영업의 Budgetary RFQ에서 사용됩니다. - {/* <span className="inline-flex items-center whitespace-nowrap"> - <Ellipsis className="size-3" /> - <span className="ml-1">버튼</span> - </span> - 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */} - </p> - </div> - </div> - </div> - - <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> - {/* <DateRangePicker - triggerSize="sm" - triggerClassName="ml-auto w-56 sm:w-60" - align="end" - shallow={false} - /> */} - </React.Suspense> - <React.Suspense - fallback={ - <DataTableSkeleton - columnCount={6} - searchableColumnCount={1} - filterableColumnCount={2} - cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} - shrinkZero - /> - } - > - <BidProjectsTable promises={promises} /> - </React.Suspense> - </Shell> - ) -} diff --git a/app/[lng]/procurement/(procurement)/budgetary-tech-sales-hull/page.tsx b/app/[lng]/procurement/(procurement)/budgetary-tech-sales-hull/page.tsx deleted file mode 100644 index b1be29db..00000000 --- a/app/[lng]/procurement/(procurement)/budgetary-tech-sales-hull/page.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { searchParamsHullCache } from "@/lib/techsales-rfq/validations" -import { getTechSalesHullRfqsWithJoin } from "@/lib/techsales-rfq/service" -import { getValidFilters } from "@/lib/data-table" -import { Shell } from "@/components/shell" -import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" -import { RFQListTable } from "@/lib/techsales-rfq/table/rfq-table" -import { type SearchParams } from "@/types/table" -import * as React from "react" - -interface HullRfqPageProps { - searchParams: Promise<SearchParams> -} - -export default async function HullRfqPage(props: HullRfqPageProps) { - // searchParams를 await하여 resolve - const searchParams = await props.searchParams - - // 해양 HULL용 파라미터 파싱 - const search = searchParamsHullCache.parse(searchParams); - const validFilters = getValidFilters(search.filters); - - // 기술영업 해양 Hull RFQ 데이터를 Promise.all로 감싸서 전달 - const promises = Promise.all([ - getTechSalesHullRfqsWithJoin({ - ...search, // 모든 파라미터 전달 (page, perPage, sort, basicFilters, filters 등) - filters: validFilters, // 고급 필터를 명시적으로 오버라이드 (파싱된 버전) - }) - ]) - - return ( - <Shell variant="fullscreen" className="h-full"> {/* fullscreen variant 사용 */} - {/* 고정 헤더 영역 */} - <div className="flex-shrink-0"> - <div className="flex items-center justify-between"> - <div> - <h2 className="text-2xl font-bold tracking-tight"> - 기술영업-해양 Hull RFQ - </h2> - </div> - </div> - </div> - - {/* 테이블 영역 - 남은 공간 모두 차지 */} - <div className="flex-1 min-h-0"> - <React.Suspense - fallback={ - <DataTableSkeleton - columnCount={8} - searchableColumnCount={2} - filterableColumnCount={3} - cellWidths={["10rem", "15rem", "12rem", "12rem", "8rem", "8rem", "10rem", "8rem"]} - shrinkZero - /> - } - > - <RFQListTable promises={promises} className="h-full" rfqType="HULL" /> - </React.Suspense> - </div> - </Shell> - ) -}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/budgetary-tech-sales-ship/page.tsx b/app/[lng]/procurement/(procurement)/budgetary-tech-sales-ship/page.tsx deleted file mode 100644 index b7bf9d15..00000000 --- a/app/[lng]/procurement/(procurement)/budgetary-tech-sales-ship/page.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { searchParamsShipCache } from "@/lib/techsales-rfq/validations" -import { getTechSalesShipRfqsWithJoin } from "@/lib/techsales-rfq/service" -import { getValidFilters } from "@/lib/data-table" -import { Shell } from "@/components/shell" -import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" -import { RFQListTable } from "@/lib/techsales-rfq/table/rfq-table" -import { type SearchParams } from "@/types/table" -import * as React from "react" - -interface RfqPageProps { - searchParams: Promise<SearchParams> -} - -export default async function RfqPage(props: RfqPageProps) { - // searchParams를 await하여 resolve - const searchParams = await props.searchParams - - // 조선용 파라미터 파싱 - const search = searchParamsShipCache.parse(searchParams); - const validFilters = getValidFilters(search.filters); - - // 기술영업 조선 RFQ 데이터를 Promise.all로 감싸서 전달 - const promises = Promise.all([ - getTechSalesShipRfqsWithJoin({ - ...search, // 모든 파라미터 전달 (page, perPage, sort, basicFilters, filters 등) - filters: validFilters, // 고급 필터를 명시적으로 오버라이드 (파싱된 버전) - }) - ]) - - return ( - <Shell variant="fullscreen" className="h-full"> {/* fullscreen variant 사용 */} - {/* 고정 헤더 영역 */} - <div className="flex-shrink-0"> - <div className="flex items-center justify-between"> - <div> - <h2 className="text-2xl font-bold tracking-tight"> - 기술영업-조선 RFQ - </h2> - </div> - </div> - </div> - - {/* 테이블 영역 - 남은 공간 모두 차지 */} - <div className="flex-1 min-h-0"> - <React.Suspense - fallback={ - <DataTableSkeleton - columnCount={8} - searchableColumnCount={2} - filterableColumnCount={3} - cellWidths={["10rem", "15rem", "12rem", "12rem", "8rem", "8rem", "10rem", "8rem"]} - shrinkZero - /> - } - > - <RFQListTable promises={promises} className="h-full" rfqType="SHIP" /> - </React.Suspense> - </div> - </Shell> - ) -}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/budgetary-tech-sales-top/page.tsx b/app/[lng]/procurement/(procurement)/budgetary-tech-sales-top/page.tsx deleted file mode 100644 index f84a9794..00000000 --- a/app/[lng]/procurement/(procurement)/budgetary-tech-sales-top/page.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { searchParamsTopCache } from "@/lib/techsales-rfq/validations" -import { getTechSalesTopRfqsWithJoin } from "@/lib/techsales-rfq/service" -import { getValidFilters } from "@/lib/data-table" -import { Shell } from "@/components/shell" -import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" -import { RFQListTable } from "@/lib/techsales-rfq/table/rfq-table" -import { type SearchParams } from "@/types/table" -import * as React from "react" - -interface HullRfqPageProps { - searchParams: Promise<SearchParams> -} - -export default async function HullRfqPage(props: HullRfqPageProps) { - // searchParams를 await하여 resolve - const searchParams = await props.searchParams - - // 해양 TOP용 파라미터 파싱 - const search = searchParamsTopCache.parse(searchParams); - const validFilters = getValidFilters(search.filters); - - // 기술영업 해양 TOP RFQ 데이터를 Promise.all로 감싸서 전달 - const promises = Promise.all([ - getTechSalesTopRfqsWithJoin({ - ...search, // 모든 파라미터 전달 (page, perPage, sort, basicFilters, filters 등) - filters: validFilters, // 고급 필터를 명시적으로 오버라이드 (파싱된 버전) - }) - ]) - - return ( - <Shell variant="fullscreen" className="h-full"> {/* fullscreen variant 사용 */} - {/* 고정 헤더 영역 */} - <div className="flex-shrink-0"> - <div className="flex items-center justify-between"> - <div> - <h2 className="text-2xl font-bold tracking-tight"> - 기술영업-해양 TOP RFQ - </h2> - </div> - </div> - </div> - - {/* 테이블 영역 - 남은 공간 모두 차지 */} - <div className="flex-1 min-h-0"> - <React.Suspense - fallback={ - <DataTableSkeleton - columnCount={8} - searchableColumnCount={2} - filterableColumnCount={3} - cellWidths={["10rem", "15rem", "12rem", "12rem", "8rem", "8rem", "10rem", "8rem"]} - shrinkZero - /> - } - > - <RFQListTable promises={promises} className="h-full" rfqType="TOP" /> - </React.Suspense> - </div> - </Shell> - ) -}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/cbe-tech/page.tsx b/app/[lng]/procurement/(procurement)/cbe-tech/page.tsx deleted file mode 100644 index 4dadc58f..00000000 --- a/app/[lng]/procurement/(procurement)/cbe-tech/page.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { type SearchParams } from "@/types/table" -import { getValidFilters } from "@/lib/data-table" -import { getAllCBE } from "@/lib/rfqs-tech/service" -import { searchParamsCBECache } from "@/lib/rfqs-tech/validations" -import { AllCbeTable } from "@/lib/cbe-tech/table/cbe-table" -import * as React from "react" -import { Shell } from "@/components/shell" -import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" - -interface IndexPageProps { - params: { - lng: string - } - searchParams: Promise<SearchParams> -} - -export default async function RfqCBEPage(props: IndexPageProps) { - const resolvedParams = await props.params - const lng = resolvedParams.lng - - // URL 쿼리 파라미터에서 타입 추출 - const searchParams = await props.searchParams - - // SearchParams 파싱 (Zod) - const search = searchParamsCBECache.parse(searchParams) - const validFilters = getValidFilters(search.filters) - - // 현재 선택된 타입의 데이터 로드 - const promises = Promise.all([ - getAllCBE({ - ...search, - filters: validFilters, - }) - ]) - - return ( - <Shell className="gap-2"> - <div className="flex items-center justify-between space-y-2"> - <div className="flex items-center justify-between space-y-2"> - <div> - <h2 className="text-2xl font-bold tracking-tight"> - Commercial Bid Evaluation - </h2> - <p className="text-muted-foreground"> - 초대된 협력업체에게 CBE를 보낼 수 있습니다. <br/> - 체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 CBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다. - </p> - </div> - </div> - </div> - - <React.Suspense - fallback={ - <DataTableSkeleton - columnCount={6} - searchableColumnCount={1} - filterableColumnCount={2} - cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} - shrinkZero - /> - } - > - <AllCbeTable promises={promises}/> - </React.Suspense> - </Shell> - ) -}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/evaluation-input/[id]/page.tsx b/app/[lng]/procurement/(procurement)/evaluation-input/[id]/page.tsx new file mode 100644 index 00000000..3a403620 --- /dev/null +++ b/app/[lng]/procurement/(procurement)/evaluation-input/[id]/page.tsx @@ -0,0 +1,22 @@ +import { EvaluationPage } from "@/lib/evaluation-submit/evaluation-page" +import { Metadata } from "next" + +export const metadata: Metadata = { + title: "평가 작성", + description: "협력업체 평가를 작성합니다", +} + +interface PageProps { + params: { + id: string + } +} + +export default function Page({ params }: PageProps) { + return <EvaluationPage /> +} + +export async function generateStaticParams() { + // 동적 경로이므로 빈 배열 반환 + return [] +}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/evaluation-input/page.tsx b/app/[lng]/procurement/(procurement)/evaluation-input/page.tsx new file mode 100644 index 00000000..2cf5449f --- /dev/null +++ b/app/[lng]/procurement/(procurement)/evaluation-input/page.tsx @@ -0,0 +1,135 @@ +import * as React from "react" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" +import { getServerSession } from "next-auth" +import { authOptions } from "@/app/api/auth/[...nextauth]/route" +import Link from "next/link" +import { Button } from "@/components/ui/button" +import { LogIn } from "lucide-react" +import { getSHIEvaluationSubmissions } from "@/lib/evaluation-submit/service" +import { getSHIEvaluationsSubmitSchema } from "@/lib/evaluation-submit/validation" +import { SHIEvaluationSubmissionsTable } from "@/lib/evaluation-submit/table/submit-table" + +interface IndexPageProps { + searchParams: Promise<SearchParams> +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = getSHIEvaluationsSubmitSchema.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + // Get session + const session = await getServerSession(authOptions) + + // Check if user is logged in + if (!session || !session.user) { + // Return login required UI instead of redirecting + return ( + <Shell className="gap-6"> + <div className="flex items-center justify-between"> + <div> + <div className="flex items-center gap-2"> + <h2 className="text-2xl font-bold tracking-tight"> + 정기평가 + </h2> + </div> + <p className="text-muted-foreground"> + 요청된 정기평가를 입력하고 제출할 수 있습니다. + </p> + </div> + </div> + + <div className="flex flex-col items-center justify-center py-12 text-center"> + <div className="rounded-lg border border-dashed p-10 shadow-sm"> + <h3 className="mb-2 text-xl font-semibold">로그인이 필요합니다</h3> + <p className="mb-6 text-muted-foreground"> + 정기평가를 확인하려면 먼저 로그인하세요. + </p> + <Button size="lg" asChild> + <Link href="/partners"> + <LogIn className="mr-2 h-4 w-4" /> + 로그인하기 + </Link> + </Button> + </div> + </div> + </Shell> + ) + } + + const userId = session.user.id + + // Validate vendorId (should be a number) + const idAsNumber = Number(userId) + + + if (isNaN(idAsNumber)) { + // Handle invalid vendor ID (this shouldn't happen if authentication is working properly) + return ( + <Shell className="gap-6"> + <div className="flex items-center justify-between"> + <div> + <h2 className="text-2xl font-bold tracking-tight"> + 정기평가 + </h2> + </div> + </div> + <div className="flex flex-col items-center justify-center py-12 text-center"> + <div className="rounded-lg border border-dashed p-10 shadow-sm"> + <h3 className="mb-2 text-xl font-semibold">계정 오류</h3> + <p className="mb-6 text-muted-foreground"> + 관리자에게 문의하세요. + </p> + </div> + </div> + </Shell> + ) + } + + // If we got here, we have a valid vendor ID + const promises = Promise.all([ + getSHIEvaluationSubmissions({ + ...search, + filters: validFilters, + }, idAsNumber) + ]) + + return ( + <Shell className="gap-2"> + <div className="flex items-center justify-between space-y-2"> + <div className="flex items-center justify-between space-y-2"> + <div> + <h2 className="text-2xl font-bold tracking-tight"> + 정기평가 + </h2> + <p className="text-muted-foreground"> + 요청된 정기평가를 입력하고 제출할 수 있습니다. + </p> + </div> + </div> + </div> + + <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> + {/* DateRangePicker can go here */} + </React.Suspense> + + <React.Suspense + fallback={ + <DataTableSkeleton + columnCount={6} + searchableColumnCount={1} + filterableColumnCount={2} + cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} + shrinkZero + /> + } + > + <SHIEvaluationSubmissionsTable promises={promises} /> + </React.Suspense> + </Shell> + ) +}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/evaluation-target-list/page.tsx b/app/[lng]/procurement/(procurement)/evaluation-target-list/page.tsx index 088ae75b..9ec30b66 100644 --- a/app/[lng]/procurement/(procurement)/evaluation-target-list/page.tsx +++ b/app/[lng]/procurement/(procurement)/evaluation-target-list/page.tsx @@ -16,7 +16,7 @@ import { Badge } from "@/components/ui/badge" import { getDefaultEvaluationYear, searchParamsEvaluationTargetsCache } from "@/lib/evaluation-target-list/validation" import { getEvaluationTargets } from "@/lib/evaluation-target-list/service" import { EvaluationTargetsTable } from "@/lib/evaluation-target-list/table/evaluation-target-table" - +import { InformationButton } from "@/components/information/information-button" export const metadata: Metadata = { title: "협력업체 평가 대상 확정", description: "협력업체 평가 대상을 확정하고 담당자를 지정합니다.", @@ -66,9 +66,12 @@ export default async function EvaluationTargetsPage(props: EvaluationTargetsPage <div className="flex items-center justify-between space-y-2"> <div className="flex items-center justify-between space-y-2"> <div className="flex items-center gap-2"> - <h2 className="text-2xl font-bold tracking-tight"> - 협력업체 평가 대상 확정 - </h2> + <div className="flex items-center gap-2"> + <h2 className="text-2xl font-bold tracking-tight"> + 협력업체 평가 대상 확정 + </h2> + <InformationButton pagePath="evcp/evaluation-target-list" /> + </div> <Badge variant="outline" className="text-sm"> {currentEvaluationYear}년도 </Badge> diff --git a/app/[lng]/procurement/(procurement)/form-list/page.tsx b/app/[lng]/procurement/(procurement)/form-list/page.tsx deleted file mode 100644 index a6cf7d9e..00000000 --- a/app/[lng]/procurement/(procurement)/form-list/page.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import * as React from "react" -import { type SearchParams } from "@/types/table" - -import { getValidFilters } from "@/lib/data-table" -import { Skeleton } from "@/components/ui/skeleton" -import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" -import { Shell } from "@/components/shell" -import { searchParamsCache } from "@/lib/form-list/validation" -import { ItemsTable } from "@/lib/items/table/items-table" -import { getFormLists } from "@/lib/form-list/service" -import { FormListsTable } from "@/lib/form-list/table/formLists-table" - - -interface IndexPageProps { - searchParams: Promise<SearchParams> -} - -export default async function IndexPage(props: IndexPageProps) { - const searchParams = await props.searchParams - const search = searchParamsCache.parse(searchParams) - - const validFilters = getValidFilters(search.filters) - - const promises = Promise.all([ - getFormLists({ - ...search, - filters: validFilters, - }), - - ]) - - return ( - <Shell className="gap-2"> - <div className="flex items-center justify-between space-y-2"> - <div className="flex items-center justify-between space-y-2"> - <div> - <h2 className="text-2xl font-bold tracking-tight"> - 레지스터 목록 from S-EDP - </h2> - <p className="text-muted-foreground"> - 협력업체 데이터 입력을 위한 레지스터 목록 리스트입니다.{" "} - {/* <span className="inline-flex items-center whitespace-nowrap"> - <Ellipsis className="size-3" /> - <span className="ml-1">버튼</span> - </span> - 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */} - </p> - </div> - </div> - </div> - - <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> - {/* <DateRangePicker - triggerSize="sm" - triggerClassName="ml-auto w-56 sm:w-60" - align="end" - shallow={false} - /> */} - </React.Suspense> - <React.Suspense - fallback={ - <DataTableSkeleton - columnCount={6} - searchableColumnCount={1} - filterableColumnCount={2} - cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} - shrinkZero - /> - } - > - <FormListsTable promises={promises} /> - </React.Suspense> - </Shell> - ) -} diff --git a/app/[lng]/procurement/(procurement)/report/page.tsx b/app/[lng]/procurement/(procurement)/report/page.tsx index 800fbd8b..adeb31aa 100644 --- a/app/[lng]/procurement/(procurement)/report/page.tsx +++ b/app/[lng]/procurement/(procurement)/report/page.tsx @@ -1,5 +1,3 @@ - -// app/procurement/dashboard/page.tsx import * as React from "react"; import { Skeleton } from "@/components/ui/skeleton"; import { Shell } from "@/components/shell"; @@ -7,29 +5,34 @@ import { ErrorBoundary } from "@/components/error-boundary"; import { getDashboardData } from "@/lib/dashboard/service"; import { DashboardClient } from "@/lib/dashboard/dashboard-client"; -// 대시보드 데이터 로딩 컴포넌트 -async function DashboardContent() { +export default async function IndexPage() { + // domain을 명시적으로 전달 + const domain = "procurement"; + try { - const data = await getDashboardData("procurement"); + // 서버에서 직접 데이터 fetch + const dashboardData = await getDashboardData(domain); - const handleRefresh = async () => { - "use server"; - return await getDashboardData("procurement"); - }; - return ( - <DashboardClient - initialData={data} - onRefresh={handleRefresh} - /> + <Shell className="gap-2"> + <DashboardClient initialData={dashboardData} /> + </Shell> ); } catch (error) { - console.error("Dashboard data loading error:", error); - throw error; + console.error("Dashboard data fetch error:", error); + return ( + <Shell className="gap-2"> + <div className="flex items-center justify-center py-12"> + <div className="text-center space-y-2"> + <p className="text-destructive">데이터를 불러오는데 실패했습니다.</p> + <p className="text-muted-foregroucdnd text-sm">{error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다."}</p> + </div> + </div> + </Shell> + ); } } -// 대시보드 로딩 스켈레톤 function DashboardSkeleton() { return ( <div className="space-y-6"> @@ -95,35 +98,3 @@ function DashboardSkeleton() { </div> ); } - -// 에러 표시 컴포넌트 -function DashboardError({ error, reset }: { error: Error; reset: () => void }) { - return ( - <div className="flex flex-col items-center justify-center py-12 space-y-4"> - <div className="text-center space-y-2"> - <h3 className="text-lg font-semibold">대시보드를 불러올 수 없습니다</h3> - <p className="text-muted-foreground"> - {error.message || "알 수 없는 오류가 발생했습니다."} - </p> - </div> - <button - onClick={reset} - className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90" - > - 다시 시도 - </button> - </div> - ); -} - -export default async function DashboardPage() { - return ( - <Shell className="gap-6"> - <ErrorBoundary fallback={DashboardError}> - <React.Suspense fallback={<DashboardSkeleton />}> - <DashboardContent /> - </React.Suspense> - </ErrorBoundary> - </Shell> - ); -} diff --git a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/cbe/page.tsx b/app/[lng]/procurement/(procurement)/rfq-tech/[id]/cbe/page.tsx deleted file mode 100644 index 84379caf..00000000 --- a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/cbe/page.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Separator } from "@/components/ui/separator" -import { type SearchParams } from "@/types/table" -import { getValidFilters } from "@/lib/data-table" -import { searchParamsCBECache } from "@/lib/rfqs-tech/validations" -import { getCBE } from "@/lib/rfqs-tech/service" -import { CbeTable } from "@/lib/rfqs-tech/cbe-table/cbe-table" - -interface IndexPageProps { - // Next.js 13 App Router에서 기본으로 주어지는 객체들 - params: { - lng: string - id: string - } - searchParams: Promise<SearchParams> -} - -export default async function RfqCBEPage(props: IndexPageProps) { - const resolvedParams = await props.params - const lng = resolvedParams.lng - const id = resolvedParams.id - - const idAsNumber = Number(id) - - // 2) SearchParams 파싱 (Zod) - // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 - const searchParams = await props.searchParams - const search = searchParamsCBECache.parse(searchParams) - const validFilters = getValidFilters(search.filters) - - const promises = Promise.all([ - getCBE({ - ...search, - filters: validFilters, - }, - idAsNumber) - ]) - - // 4) 렌더링 - return ( - <div className="space-y-6"> - <div> - <h3 className="text-lg font-medium"> - Commercial Bid Evaluation - </h3> - <p className="text-sm text-muted-foreground"> - 초대된 협력업체에게 CBE를 보낼 수 있습니다. <br />"발행하기" 버튼을 통해 CBE를 전송하면 CBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다. - </p> - </div> - <Separator /> - <div> - <CbeTable promises={promises} rfqId={idAsNumber} /> - </div> - </div> - ) -}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/layout.tsx b/app/[lng]/procurement/(procurement)/rfq-tech/[id]/layout.tsx deleted file mode 100644 index 0bb62fe0..00000000 --- a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/layout.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Metadata } from "next" -import Link from "next/link" -import { Separator } from "@/components/ui/separator" -import { SidebarNav } from "@/components/layout/sidebar-nav" -import { RfqViewWithItems } from "@/db/schema/rfq" -import { findRfqById } from "@/lib/rfqs-tech/service" -import { formatDate } from "@/lib/utils" -import { Button } from "@/components/ui/button" -import { ArrowLeft } from "lucide-react" - -export const metadata: Metadata = { - title: "Vendor Detail", -} - -export default async function RfqLayout({ - children, - params, -}: { - children: React.ReactNode - params: { lng: string, id: string } -}) { - - // 1) URL 파라미터에서 id 추출, Number로 변환 - const resolvedParams = await params - const lng = resolvedParams.lng - const id = resolvedParams.id - - const idAsNumber = Number(id) - // 2) DB에서 해당 협력업체 정보 조회 - const rfq: RfqViewWithItems | null = await findRfqById(idAsNumber) - - // 3) 사이드바 메뉴 - const sidebarNavItems = [ - { - title: "Matched Vendors", - href: `/${lng}/evcp/rfq-tech/${id}`, - }, - { - title: "TBE", - href: `/${lng}/evcp/rfq-tech/${id}/tbe`, - }, - { - title: "CBE", - href: `/${lng}/evcp/rfq-tech/${id}/cbe`, - }, - - ] - - return ( - <> - <div className="container py-6"> - <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow"> - <div className="hidden space-y-6 p-10 pb-16 md:block"> - <div className="flex items-center justify-end mb-4"> - <Link href={`/${lng}/evcp/rfq`} passHref> - <Button variant="ghost" className="flex items-center text-primary hover:text-primary/80 transition-colors p-0 h-auto"> - <ArrowLeft className="mr-1 h-4 w-4" /> - <span>RFQ 목록으로 돌아가기</span> - </Button> - </Link> - </div> - <div className="space-y-0.5"> - {/* 4) 협력업체 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */} - <h2 className="text-2xl font-bold tracking-tight"> - {rfq - ? `${rfq.projectCode ?? ""} ${rfq.rfqCode ?? ""} 관리` - : "Loading RFQ..."} - </h2> - - <p className="text-muted-foreground"> - {rfq - ? `${rfq.description ?? ""} ${rfq.lines.map(line => line.itemCode).join(", ")}` - : ""} - </p> - <h3>Due Date:{rfq && rfq?.dueDate && <strong>{formatDate(rfq?.dueDate)}</strong>}</h3> - </div> - <Separator className="my-6" /> - <div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0"> - <aside className="lg:w-64 flex-shrink-0"> - <SidebarNav items={sidebarNavItems} /> - </aside> - <div className="lg:w-[calc(100%-16rem)] overflow-auto">{children}</div> - </div> - </div> - </section> - </div> - </> - ) -}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/page.tsx b/app/[lng]/procurement/(procurement)/rfq-tech/[id]/page.tsx deleted file mode 100644 index 007270a1..00000000 --- a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/page.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Separator } from "@/components/ui/separator" -import { type SearchParams } from "@/types/table" -import { getValidFilters } from "@/lib/data-table" -import { getMatchedVendors } from "@/lib/rfqs-tech/service" -import { searchParamsMatchedVCache } from "@/lib/rfqs-tech/validations" -import { MatchedVendorsTable } from "@/lib/rfqs-tech/vendor-table/vendors-table" - -interface IndexPageProps { - // Next.js 13 App Router에서 기본으로 주어지는 객체들 - params: { - lng: string - id: string - } - searchParams: Promise<SearchParams> -} - -export default async function RfqPage(props: IndexPageProps) { - const resolvedParams = await props.params - const lng = resolvedParams.lng - const id = resolvedParams.id - - const idAsNumber = Number(id) - - // 2) SearchParams 파싱 (Zod) - // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 - const searchParams = await props.searchParams - const search = searchParamsMatchedVCache.parse(searchParams) - const validFilters = getValidFilters(search.filters) - - const promises = Promise.all([ - getMatchedVendors({ - ...search, - filters: validFilters, - }, - idAsNumber) - ]) - - // 4) 렌더링 - return ( - <div className="space-y-6"> - <div> - <h3 className="text-lg font-medium"> - Vendors - </h3> - <p className="text-sm text-muted-foreground"> - 등록된 협력업체 중에서 이 RFQ 아이템에 매칭되는 업체를 보여줍니다. <br/>"발행하기" 버튼을 통해 RFQ를 전송하면 첨부파일과 함께 RFQ 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다. - </p> - </div> - <Separator /> - <div> - <MatchedVendorsTable promises={promises} rfqId={idAsNumber}/> - </div> - </div> - ) -}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/tbe/page.tsx b/app/[lng]/procurement/(procurement)/rfq-tech/[id]/tbe/page.tsx deleted file mode 100644 index 4b226cdc..00000000 --- a/app/[lng]/procurement/(procurement)/rfq-tech/[id]/tbe/page.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Separator } from "@/components/ui/separator" -import { type SearchParams } from "@/types/table" -import { getValidFilters } from "@/lib/data-table" -import { getTBE } from "@/lib/rfqs-tech/service" -import { searchParamsTBECache } from "@/lib/rfqs-tech/validations" -import { TbeTable } from "@/lib/rfqs-tech/tbe-table/tbe-table" - -interface IndexPageProps { - // Next.js 13 App Router에서 기본으로 주어지는 객체들 - params: { - lng: string - id: string - } - searchParams: Promise<SearchParams> -} - -export default async function RfqTBEPage(props: IndexPageProps) { - const resolvedParams = await props.params - const lng = resolvedParams.lng - const id = resolvedParams.id - - const idAsNumber = Number(id) - - // 2) SearchParams 파싱 (Zod) - // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 - const searchParams = await props.searchParams - const search = searchParamsTBECache.parse(searchParams) - const validFilters = getValidFilters(search.filters) - - const promises = Promise.all([ - getTBE({ - ...search, - filters: validFilters, - }, - idAsNumber) - ]) - - // 4) 렌더링 - return ( - <div className="space-y-6"> - <div> - <h3 className="text-lg font-medium"> - Technical Bid Evaluation - </h3> - <p className="text-sm text-muted-foreground"> - 초대된 협력업체에게 TBE를 보낼 수 있습니다. <br/>"발행하기" 버튼을 통해 TBE를 전송하면 첨부파일과 함께 TBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다. - </p> - </div> - <Separator /> - <div> - <TbeTable promises={promises} rfqId={idAsNumber}/> - </div> - </div> - ) -}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/rfq-tech/page.tsx b/app/[lng]/procurement/(procurement)/rfq-tech/page.tsx deleted file mode 100644 index f35b3632..00000000 --- a/app/[lng]/procurement/(procurement)/rfq-tech/page.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import * as React from "react" -import { type SearchParams } from "@/types/table" - -import { getValidFilters } from "@/lib/data-table" -import { Skeleton } from "@/components/ui/skeleton" -import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" -import { Shell } from "@/components/shell" - -import { searchParamsCache } from "@/lib/rfqs-tech/validations" -import { getRfqs, getRfqStatusCounts } from "@/lib/rfqs-tech/service" -import { RfqsTable } from "@/lib/rfqs-tech/table/rfqs-table" -import { getAllOffshoreItems } from "@/lib/items-tech/service" - -interface RfqPageProps { - searchParams: Promise<SearchParams>; - title: string; - description: string; -} - -export default async function RfqPage({ - searchParams, - title = "기술영업 해양 RFQ", - description = "기술영업 해양 RFQ를 등록하고 관리할 수 있습니다." -}: RfqPageProps) { - const search = searchParamsCache.parse(await searchParams) - - const validFilters = getValidFilters(search.filters) - - const promises = Promise.all([ - getRfqs({ - ...search, - filters: validFilters, - }), - getRfqStatusCounts(), - getAllOffshoreItems() - ]) - - return ( - <Shell className="gap-2"> - <div className="flex items-center justify-between space-y-2"> - <div className="flex items-center justify-between space-y-2"> - <div> - <h2 className="text-2xl font-bold tracking-tight"> - {title} - </h2> - <p className="text-muted-foreground"> - {description} - </p> - </div> - </div> - </div> - - <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> - {/* <DateRangePicker - triggerSize="sm" - triggerClassName="ml-auto w-56 sm:w-60" - align="end" - shallow={false} - /> */} - </React.Suspense> - <React.Suspense - fallback={ - <DataTableSkeleton - columnCount={6} - searchableColumnCount={1} - filterableColumnCount={2} - cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} - shrinkZero - /> - } - > - <RfqsTable promises={promises} /> - </React.Suspense> - </Shell> - ) -}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/tag-numbering/page.tsx b/app/[lng]/procurement/(procurement)/tag-numbering/page.tsx deleted file mode 100644 index 44695259..00000000 --- a/app/[lng]/procurement/(procurement)/tag-numbering/page.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import * as React from "react" -import { type SearchParams } from "@/types/table" - -import { getValidFilters } from "@/lib/data-table" -import { Skeleton } from "@/components/ui/skeleton" -import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" -import { Shell } from "@/components/shell" -import { searchParamsCache } from "@/lib/tag-numbering/validation" -import { getTagNumbering } from "@/lib/tag-numbering/service" -import { TagNumberingTable } from "@/lib/tag-numbering/table/tagNumbering-table" - - -interface IndexPageProps { - searchParams: Promise<SearchParams> -} - -export default async function IndexPage(props: IndexPageProps) { - const searchParams = await props.searchParams - const search = searchParamsCache.parse(searchParams) - - const validFilters = getValidFilters(search.filters) - - const promises = Promise.all([ - getTagNumbering({ - ...search, - filters: validFilters, - }), - - ]) - - return ( - <Shell className="gap-2"> - <div className="flex items-center justify-between space-y-2"> - <div className="flex items-center justify-between space-y-2"> - <div> - <h2 className="text-2xl font-bold tracking-tight"> - 태그 타입 목록 from S-EDP - </h2> - <p className="text-muted-foreground"> - 태그 넘버링을 위한 룰셋을 S-EDP로부터 가져오고 확인할 수 있습니다{" "} - {/* <span className="inline-flex items-center whitespace-nowrap"> - <Ellipsis className="size-3" /> - <span className="ml-1">버튼</span> - </span> - 을 통해 담당자 연락처, 입찰 이력, 계약 이력, 패키지 내용 등을 확인 할 수 있습니다. */} - </p> - </div> - </div> - </div> - - <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> - {/* <DateRangePicker - triggerSize="sm" - triggerClassName="ml-auto w-56 sm:w-60" - align="end" - shallow={false} - /> */} - </React.Suspense> - <React.Suspense - fallback={ - <DataTableSkeleton - columnCount={6} - searchableColumnCount={1} - filterableColumnCount={2} - cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} - shrinkZero - /> - } - > - <TagNumberingTable promises={promises} /> - </React.Suspense> - </Shell> - ) -} diff --git a/app/[lng]/procurement/(procurement)/tasks/page.tsx b/app/[lng]/procurement/(procurement)/tasks/page.tsx deleted file mode 100644 index 91b946fb..00000000 --- a/app/[lng]/procurement/(procurement)/tasks/page.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import * as React from "react" -import { type SearchParams } from "@/types/table" - -import { getValidFilters } from "@/lib/data-table" -import { Skeleton } from "@/components/ui/skeleton" -import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" -import { DateRangePicker } from "@/components/date-range-picker" -import { Shell } from "@/components/shell" - -import { FeatureFlagsProvider } from "@/lib/tasks/table/feature-flags-provider" -import { TasksTable } from "@/lib/tasks/table/tasks-table" -import { - getTaskPriorityCounts, - getTasks, - getTaskStatusCounts, -} from "@/lib/tasks/service" -import { searchParamsCache } from "@/lib/tasks/validations" - -interface IndexPageProps { - searchParams: Promise<SearchParams> -} - -export default async function IndexPage(props: IndexPageProps) { - const searchParams = await props.searchParams - const search = searchParamsCache.parse(searchParams) - - const validFilters = getValidFilters(search.filters) - - const promises = Promise.all([ - getTasks({ - ...search, - filters: validFilters, - }), - getTaskStatusCounts(), - getTaskPriorityCounts(), - ]) - - return ( - <Shell className="gap-2"> - <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> - <DateRangePicker - triggerSize="sm" - triggerClassName="ml-auto w-56 sm:w-60" - align="end" - shallow={false} - /> - </React.Suspense> - <React.Suspense - fallback={ - <DataTableSkeleton - columnCount={6} - searchableColumnCount={1} - filterableColumnCount={2} - cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} - shrinkZero - /> - } - > - <TasksTable promises={promises} /> - </React.Suspense> - </Shell> - ) -} diff --git a/app/[lng]/procurement/(procurement)/tbe-tech/page.tsx b/app/[lng]/procurement/(procurement)/tbe-tech/page.tsx deleted file mode 100644 index 17b01ce2..00000000 --- a/app/[lng]/procurement/(procurement)/tbe-tech/page.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { type SearchParams } from "@/types/table" -import { getValidFilters } from "@/lib/data-table" -import { getAllTBE } from "@/lib/rfqs-tech/service" -import { searchParamsTBECache } from "@/lib/rfqs-tech/validations" -import { AllTbeTable } from "@/lib/tbe-tech/table/tbe-table" -import * as React from "react" -import { Shell } from "@/components/shell" -import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" - -interface IndexPageProps { - params: { - lng: string - } - searchParams: Promise<SearchParams> -} - -export default async function RfqTBEPage(props: IndexPageProps) { - const resolvedParams = await props.params - const lng = resolvedParams.lng - - // URL 쿼리 파라미터에서 타입 추출 - const searchParams = await props.searchParams - - // SearchParams 파싱 (Zod) - const search = searchParamsTBECache.parse(searchParams) - const validFilters = getValidFilters(search.filters) - - // 현재 선택된 타입의 데이터 로드 - const promises = Promise.all([ - getAllTBE({ - ...search, - filters: validFilters, - }) - ]) - - return ( - <Shell className="gap-2"> - <div className="flex items-center justify-between space-y-2"> - <div className="flex items-center justify-between space-y-2"> - <div> - <h2 className="text-2xl font-bold tracking-tight"> - Technical Bid Evaluation - </h2> - <p className="text-muted-foreground"> - 초대된 협력업체에게 TBE를 보낼 수 있습니다. <br/> - 체크박스 선택을 하면 초대 버튼이 활성화됩니다. 버튼 클릭 후 첨부파일을 함께 전송하면 TBE 내용이 메일로 전달되고 eVCP에도 협력업체가 입력할 수 있게 자동 생성됩니다. - </p> - </div> - </div> - </div> - - <React.Suspense - fallback={ - <DataTableSkeleton - columnCount={6} - searchableColumnCount={1} - filterableColumnCount={2} - cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} - shrinkZero - /> - } - > - <AllTbeTable promises={promises}/> - </React.Suspense> - </Shell> - ) -}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/tech-project-avl/page.tsx b/app/[lng]/procurement/(procurement)/tech-project-avl/page.tsx deleted file mode 100644 index d942c5c5..00000000 --- a/app/[lng]/procurement/(procurement)/tech-project-avl/page.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import * as React from "react"
-import { redirect } from "next/navigation"
-import { getServerSession } from "next-auth/next"
-import { authOptions } from "@/app/api/auth/[...nextauth]/route"
-import { SearchParams } from "@/types/table"
-import { searchParamsCache } from "@/lib/tech-project-avl/validations"
-import { Skeleton } from "@/components/ui/skeleton"
-import { Shell } from "@/components/shell"
-import { AcceptedQuotationsTable } from "@/lib/tech-project-avl/table/accepted-quotations-table"
-import { getAcceptedTechSalesVendorQuotations } from "@/lib/techsales-rfq/service"
-import { getValidFilters } from "@/lib/data-table"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Ellipsis } from "lucide-react"
-
-export interface PageProps {
- params: Promise<{ lng: string }>
- searchParams: Promise<SearchParams>
-}
-
-export default async function AcceptedQuotationsPage({
- params,
- searchParams,
-}: PageProps) {
- const { lng } = await params
-
- const session = await getServerSession(authOptions)
- if (!session) {
- redirect(`/${lng}/auth/signin`)
- }
-
- const search = await searchParams
- const { page, perPage, sort, filters, search: searchText } = searchParamsCache.parse(search)
- const validFilters = getValidFilters(filters ?? [])
-
- const { data, pageCount } = await getAcceptedTechSalesVendorQuotations({
- page,
- perPage: perPage ?? 10,
- sort,
- search: searchText,
- filters: validFilters,
- })
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <h2 className="text-2xl font-bold tracking-tight">
- 승인된 견적서(해양TOP,HULL)
- </h2>
- <p className="text-muted-foreground">
- 기술영업 승인 견적서에 대한 요약 정보를 확인하고{" "}
- <span className="inline-flex items-center whitespace-nowrap">
- <Ellipsis className="size-3" />
- <span className="ml-1">버튼</span>
- </span>
- 을 통해 RFQ 코드, 설명, 업체명, 업체 코드 등의 상세 정보를 확인할 수 있습니다.
- </p>
- </div>
- </div>
- </div>
-
- <React.Suspense fallback={<Skeleton className="h-7 w-52" />}>
- {/* Date range picker can be added here if needed */}
- </React.Suspense>
-
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={12}
- searchableColumnCount={2}
- filterableColumnCount={4}
- cellWidths={["10rem", "15rem", "12rem", "10rem", "10rem", "12rem", "8rem", "12rem", "10rem", "8rem", "10rem", "10rem"]}
- shrinkZero
- />
- }
- >
- <AcceptedQuotationsTable
- data={data}
- pageCount={pageCount}
- />
- </React.Suspense>
- </Shell>
- )
-}
diff --git a/app/[lng]/procurement/(procurement)/tech-vendor-candidates/page.tsx b/app/[lng]/procurement/(procurement)/tech-vendor-candidates/page.tsx deleted file mode 100644 index 3923863a..00000000 --- a/app/[lng]/procurement/(procurement)/tech-vendor-candidates/page.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from "react" -import { type SearchParams } from "@/types/table" - -import { getValidFilters } from "@/lib/data-table" -import { Skeleton } from "@/components/ui/skeleton" -import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" -import { Shell } from "@/components/shell" - -import { getVendorCandidateCounts, getVendorCandidates } from "@/lib/tech-vendor-candidates/service" -import { searchParamsTechCandidateCache } from "@/lib/tech-vendor-candidates/validations" -import { VendorCandidateTable as TechVendorCandidateTable } from "@/lib/tech-vendor-candidates/table/candidates-table" -import { DateRangePicker } from "@/components/date-range-picker" - -interface IndexPageProps { - searchParams: Promise<SearchParams> -} - -export default async function IndexPage(props: IndexPageProps) { - const searchParams = await props.searchParams - const search = searchParamsTechCandidateCache.parse(searchParams) - - const validFilters = getValidFilters(search.filters) - - const promises = Promise.all([ - getVendorCandidates({ - ...search, - filters: validFilters, - }), - getVendorCandidateCounts() - ]) - - return ( - <Shell className="gap-2"> - - <div className="flex items-center justify-between space-y-2"> - <div className="flex items-center justify-between space-y-2"> - <div> - <h2 className="text-2xl font-bold tracking-tight"> - Vendor Candidates Management - </h2> - <p className="text-muted-foreground"> - 수집한 협력업체 후보를 등록하고 초대 메일을 송부할 수 있습니다. - </p> - </div> - </div> - </div> - - {/* 수집일 라벨과 DateRangePicker를 함께 배치 */} - <div className="flex items-center justify-start gap-2"> - {/* <span className="text-sm font-medium">수집일 기간 설정: </span> */} - <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> - <DateRangePicker - triggerSize="sm" - triggerClassName="w-56 sm:w-60" - align="end" - shallow={false} - showClearButton={true} - placeholder="수집일 날짜 범위를 고르세요" - /> - </React.Suspense> - </div> - - <React.Suspense - fallback={ - <DataTableSkeleton - columnCount={6} - searchableColumnCount={1} - filterableColumnCount={2} - cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} - shrinkZero - /> - } - > - <TechVendorCandidateTable promises={promises}/> - </React.Suspense> - </Shell> - ) -}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/items/page.tsx b/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/items/page.tsx deleted file mode 100644 index 69c36576..00000000 --- a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/items/page.tsx +++ /dev/null @@ -1,48 +0,0 @@ -// import { Separator } from "@/components/ui/separator" -// import { getTechVendorById, getVendorItemsByType } from "@/lib/tech-vendors/service" -// import { type SearchParams } from "@/types/table" -// import { TechVendorItemsTable } from "@/lib/tech-vendors/items-table/item-table" - -// interface IndexPageProps { -// // Next.js 13 App Router에서 기본으로 주어지는 객체들 -// params: { -// lng: string -// id: string -// } -// searchParams: Promise<SearchParams> -// } - -// export default async function TechVendorItemsPage(props: IndexPageProps) { -// const resolvedParams = await props.params -// const id = resolvedParams.id - -// const idAsNumber = Number(id) - -// // 벤더 정보 가져오기 (벤더 타입 필요) -// const vendorInfo = await getTechVendorById(idAsNumber) -// const vendorType = vendorInfo.data?.techVendorType || "조선" - -// const promises = getVendorItemsByType(idAsNumber, vendorType) - -// // 4) 렌더링 -// return ( -// <div className="space-y-6"> -// <div> -// <h3 className="text-lg font-medium"> -// 공급품목 -// </h3> -// <p className="text-sm text-muted-foreground"> -// 기술영업 벤더의 공급 가능한 품목을 확인하세요. -// </p> -// </div> -// <Separator /> -// <div> -// <TechVendorItemsTable -// promises={promises} -// vendorId={idAsNumber} -// vendorType={vendorType} -// /> -// </div> -// </div> -// ) -// }
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/layout.tsx b/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/layout.tsx deleted file mode 100644 index 7c389720..00000000 --- a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/layout.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { Metadata } from "next" - -import { Separator } from "@/components/ui/separator" -import { SidebarNav } from "@/components/layout/sidebar-nav" -import { findTechVendorById } from "@/lib/tech-vendors/service" -import { TechVendor } from "@/db/schema/techVendors" -import { Button } from "@/components/ui/button" -import { ArrowLeft } from "lucide-react" -import Link from "next/link" -export const metadata: Metadata = { - title: "Tech Vendor Detail", -} - -export default async function SettingsLayout({ - children, - params, -}: { - children: React.ReactNode - params: { lng: string , id: string} -}) { - - // 1) URL 파라미터에서 id 추출, Number로 변환 - const resolvedParams = await params - const lng = resolvedParams.lng - const id = resolvedParams.id - - const idAsNumber = Number(id) - // 2) DB에서 해당 협력업체 정보 조회 - const vendor: TechVendor | null = await findTechVendorById(idAsNumber) - - // 3) 사이드바 메뉴 - const sidebarNavItems = [ - { - title: "연락처", - href: `/${lng}/evcp/tech-vendors/${id}/info`, - }, - // { - // title: "자재 리스트", - // href: `/${lng}/evcp/tech-vendors/${id}/info/items`, - // }, - // { - // title: "견적 히스토리", - // href: `/${lng}/evcp/tech-vendors/${id}/info/rfq-history`, - // }, - ] - - return ( - <> - <div className="container py-6"> - <section className="overflow-hidden rounded-[0.5rem] border bg-background shadow"> - <div className="hidden space-y-6 p-10 pb-16 md:block"> - {/* RFQ 목록으로 돌아가는 링크 추가 */} - <div className="flex items-center justify-end mb-4"> - <Link href={`/${lng}/evcp/tech-vendors`} passHref> - <Button variant="ghost" className="flex items-center text-primary hover:text-primary/80 transition-colors p-0 h-auto"> - <ArrowLeft className="mr-1 h-4 w-4" /> - <span>기술영업 벤더 목록으로 돌아가기</span> - </Button> - </Link> - </div> - <div className="space-y-0.5"> - {/* 4) 협력업체 정보가 있으면 코드 + 이름 + "상세 정보" 표기 */} - <h2 className="text-2xl font-bold tracking-tight"> - {vendor - ? `${vendor.vendorCode ?? ""} - ${vendor.vendorName} 상세 정보` - : "Loading Vendor..."} - </h2> - <p className="text-muted-foreground">기술영업 벤더 관련 상세사항을 확인하세요.</p> - </div> - <Separator className="my-6" /> - <div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0"> - <aside className="-mx-4 lg:w-1/5"> - <SidebarNav items={sidebarNavItems} /> - </aside> - <div className="flex-1">{children}</div> - </div> - </div> - </section> - </div> - </> - ) -}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/page.tsx b/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/page.tsx deleted file mode 100644 index a57d6df7..00000000 --- a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/page.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Separator } from "@/components/ui/separator" -import { getTechVendorContacts } from "@/lib/tech-vendors/service" -import { type SearchParams } from "@/types/table" -import { getValidFilters } from "@/lib/data-table" -import { searchParamsContactCache } from "@/lib/tech-vendors/validations" -import { TechVendorContactsTable } from "@/lib/tech-vendors/contacts-table/contact-table" - -interface IndexPageProps { - // Next.js 13 App Router에서 기본으로 주어지는 객체들 - params: { - lng: string - id: string - } - searchParams: Promise<SearchParams> -} - -export default async function SettingsAccountPage(props: IndexPageProps) { - const resolvedParams = await props.params - const id = resolvedParams.id - - const idAsNumber = Number(id) - - // 2) SearchParams 파싱 (Zod) - // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼 - const searchParams = await props.searchParams - const search = searchParamsContactCache.parse(searchParams) - const validFilters = getValidFilters(search.filters) - - - - const promises = Promise.all([ - getTechVendorContacts({ - ...search, - filters: validFilters, - }, - idAsNumber) - ]) - // 4) 렌더링 - return ( - <div className="space-y-6"> - <div> - <h3 className="text-lg font-medium"> - Contacts - </h3> - <p className="text-sm text-muted-foreground"> - 업무별 담당자 정보를 확인하세요. - </p> - </div> - <Separator /> - <div> - <TechVendorContactsTable promises={promises} vendorId={idAsNumber}/> - </div> - </div> - ) -}
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/rfq-history/page.tsx b/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/rfq-history/page.tsx deleted file mode 100644 index 4ed2b39f..00000000 --- a/app/[lng]/procurement/(procurement)/tech-vendors/[id]/info/rfq-history/page.tsx +++ /dev/null @@ -1,55 +0,0 @@ -// import { Separator } from "@/components/ui/separator"
-// import { getRfqHistory } from "@/lib/vendors/service"
-// import { type SearchParams } from "@/types/table"
-// import { getValidFilters } from "@/lib/data-table"
-// import { searchParamsRfqHistoryCache } from "@/lib/vendors/validations"
-// import { TechVendorRfqHistoryTable } from "@/lib/tech-vendors/rfq-history-table/rfq-history-table"
-
-// interface IndexPageProps {
-// // Next.js 13 App Router에서 기본으로 주어지는 객체들
-// params: {
-// lng: string
-// id: string
-// }
-// searchParams: Promise<SearchParams>
-// }
-
-// export default async function RfqHistoryPage(props: IndexPageProps) {
-// const resolvedParams = await props.params
-// const lng = resolvedParams.lng
-// const id = resolvedParams.id
-
-// const idAsNumber = Number(id)
-
-// // 2) SearchParams 파싱 (Zod)
-// // - "filters", "page", "perPage", "sort" 등 contact 전용 컬럼
-// const searchParams = await props.searchParams
-// const search = searchParamsRfqHistoryCache.parse(searchParams)
-// const validFilters = getValidFilters(search.filters)
-
-// const promises = Promise.all([
-// getRfqHistory({
-// ...search,
-// filters: validFilters,
-// },
-// idAsNumber)
-// ])
-
-// // 4) 렌더링
-// return (
-// <div className="space-y-6">
-// <div>
-// <h3 className="text-lg font-medium">
-// RFQ History
-// </h3>
-// <p className="text-sm text-muted-foreground">
-// 협력업체의 RFQ 참여 이력을 확인할 수 있습니다.
-// </p>
-// </div>
-// <Separator />
-// <div>
-// <TechVendorRfqHistoryTable promises={promises} vendorId={idAsNumber} />
-// </div>
-// </div>
-// )
-// }
\ No newline at end of file diff --git a/app/[lng]/procurement/(procurement)/tech-vendors/page.tsx b/app/[lng]/procurement/(procurement)/tech-vendors/page.tsx deleted file mode 100644 index 8f542f59..00000000 --- a/app/[lng]/procurement/(procurement)/tech-vendors/page.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import * as React from "react"
-import { type SearchParams } from "@/types/table"
-
-import { getValidFilters } from "@/lib/data-table"
-import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"
-import { Shell } from "@/components/shell"
-
-import { searchParamsCache } from "@/lib/tech-vendors/validations"
-import { getTechVendors, getTechVendorStatusCounts } from "@/lib/tech-vendors/service"
-import { TechVendorsTable } from "@/lib/tech-vendors/table/tech-vendors-table"
-import { TechVendorContainer } from "@/components/tech-vendors/tech-vendor-container"
-
-interface IndexPageProps {
- searchParams: Promise<SearchParams>
-}
-
-export default async function IndexPage(props: IndexPageProps) {
- const searchParams = await props.searchParams
- const search = searchParamsCache.parse(searchParams)
-
- const validFilters = getValidFilters(search.filters)
-
- // 벤더 타입 정의
- const vendorTypes = [
- { id: "all", name: "전체", value: "" },
- { id: "ship", name: "조선", value: "조선" },
- { id: "top", name: "해양TOP", value: "해양TOP" },
- { id: "hull", name: "해양HULL", value: "해양HULL" },
- ]
-
- const promises = Promise.all([
- getTechVendors({
- ...search,
- filters: validFilters,
- }),
- getTechVendorStatusCounts(),
- ])
-
- return (
- <Shell className="gap-4">
- <React.Suspense
- fallback={
- <DataTableSkeleton
- columnCount={6}
- searchableColumnCount={1}
- filterableColumnCount={2}
- cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]}
- shrinkZero
- />
- }
- >
- <TechVendorContainer vendorTypes={vendorTypes}>
- <TechVendorsTable promises={promises} />
- </TechVendorContainer>
- </React.Suspense>
- </Shell>
- )
-}
\ No newline at end of file |
